package com.sora.utils.excel;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import com.sora.utils.ServletUtils;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Classname ExcelUtils
 * @Description Excel常用方法
 * @Date 2023/07/01 14:04
 * @Author by Sora33
 */
public class ExcelUtils {

    public static final Logger logger = LoggerFactory.getLogger(ExcelUtils.class);

    /**
     * MIME类型的TYPE 针对下载文件方法 根据文件的后缀名获取对应的MIME类型
     */
    private static final HashMap<String, String> MIME_MAP = new HashMap<>(){{
        // 文本文件
        put("txt", "text/plain");
        put("csv", "text/csv");
        put("html", "text/html");
        put("css", "text/css");
        put("xml", "text/xml");
        put("json", "application/json");

        // 图像文件
        put("jpg", "image/jpeg");
        put("jpeg", "image/jpeg");
        put("png", "image/png");
        put("gif", "image/gif");
        put("bmp", "image/bmp");
        put("svg", "image/svg+xml");

        // 音频文件
        put("mp3", "audio/mpeg");
        put("wav", "audio/wav");
        put("ogg", "audio/ogg");
        put("flac", "audio/flac");

        // 视频文件
        put("mp4", "video/mp4");
        put("mov", "video/quicktime");
        put("avi", "video/x-msvideo");
        put("mkv", "video/x-matroska");

        // PDF 文件
        put("pdf", "application/pdf");

        // Excel 文件
        put("xls", "application/vnd.ms-excel");
        put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        // Word 文件
        put("doc", "application/msword");
        put("docx", "application/msword");
    }};

    /**
     * List导出Excel文件
     * @param list list类型的数据
     * @param fileName 文件名
     * @param cls 类
     * @param <T>
     */
    public static <T> void exportToExcel(List<T> list, String fileName, Class<?> cls) {

        // 创建导出参数
        ExportParams exportParams = new ExportParams();
        // 装载Style风格
        exportParams.setStyle(ExcelStyle.class);

        // 获取Excel工作簿对象
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, cls, list);

        workbookImport(workbook, fileName);
    }


    /**
     * 导入Excel文件，读取为集合并返回 返回单位为 sheet，如集合中第一个元素就是 sheet0，第二个为 sheet1，以此类推…
     * @param multipartFile
     * @param cls
     * @return
     */
    public static <T> List<List<T>> importExcelToList(MultipartFile multipartFile, Class<T> cls) {
        List<List<T>> result = new ArrayList<>();
        File file = null;
        try {
            // 将 MultipartFile 转换为临时 File
            file = File.createTempFile("temp", ".xlsx");
            multipartFile.transferTo(file);
            // Sheet 总数
            Workbook workbook = WorkbookFactory.create(file);
            int sheetCount = workbook.getNumberOfSheets();
            // 遍历每个 Sheet 并加入到返回结果中
            ImportParams params = new ImportParams();
            for (int i = 0; i < sheetCount; i++) {
                params.setStartSheetIndex(i);
                List<T> sheetData = ExcelImportUtil.importExcel(file, cls, params);
                result.add(sheetData);
            }
        } catch (IOException e) {
            logger.error("[Excel导入失败，IO读写异常！操作类：[{}]", cls, e);
        } finally {
            if (file != null && file.exists()) {
                file.delete(); // 删除临时文件
            }
        }
        return result;
    }

    /**
     * 将 List 数据写入到指定 Excel 文件（覆盖原有数据）
     *
     * @param filePath Excel 文件路径（如：D:/data/users.xlsx）
     * @param list     要写入的数据集合
     * @param cls      数据类型（如：User.class）
     * @param <T>      泛型类型
     * @throws IOException 如果文件写入失败
     */
    public static <T> void writeExcelToPath(String filePath, List<T> list, Class<T> cls) {
        // 创建导出参数（可自定义样式）
        ExportParams exportParams = new ExportParams();
        exportParams.setStyle(ExcelStyle.class);
        // 生成 Excel 工作簿
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, cls, list);
        // 写入到文件（覆盖模式）
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            workbook.write(fos);
        } catch (FileNotFoundException e) {
            logger.error("文件未找到！", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            logger.error("IO写入错误！", e);
            throw new RuntimeException(e);
        } finally {
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (IOException e) {
                    logger.error("IO写入错误！", e);
                }
            }
        }
    }

    /**
     * 将 List 数据写入到指定 Excel 文件(多个 sheet)（覆盖原有数据）
     *
     * @param sheetDataList     要写入的数据集合的集合
     * @param cls      数据类型（如：User.class）
     * @param <T>      泛型类型
     * @throws IOException 如果文件写入失败
     */
    public static <T> void writeManyListToExcel(List<List<T>> sheetDataList, String fileName, Class<T> cls) {
        // 创建 Excel 工作簿
        Workbook workbook;
        ArrayList<Map<String, Object>> maps = new ArrayList<>();
        // 遍历每个 List<T>（每个 List 对应一个 Sheet）
        for (int i = 0; i < sheetDataList.size(); i++) {
            // 设置导出参数
            ExportParams exportParams = new ExportParams();
            exportParams.setStyle(ExcelStyle.class);
            exportParams.setSheetName("Sheet" + (i + 1));

            // 加入到数据集合中
            HashMap<String, Object> mapTemp = new HashMap<>();
            mapTemp.put("title", exportParams);
            mapTemp.put("entity", cls);
            mapTemp.put("data", sheetDataList.get(i));
            maps.add(mapTemp);
        }
        workbook = ExcelExportUtil.exportExcel(maps, ExcelType.XSSF);

        workbookImport(workbook, fileName);
    }

    /**
     * 通过 workbook 对象和 fileName 写入文件到响应
     * @param workbook
     * @param fileName
     */
    private static void workbookImport(Workbook workbook, String fileName) {
        // 获取响应并设置响应头
        HttpServletResponse response = ServletUtils.getResponse();
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
        response.setHeader("Content-disposition", "attachment;filename=" + encodedFileName + ".xlsx");

        try {
            // 写入到响应对象中
            workbook.write(response.getOutputStream());
            workbook.close();
        } catch (IOException e) {
            logger.error("IO写入错误！", e);
        }
    }


    /**
     * 下载文件
     * @param filePath
     */
    public static void downloadFile(String filePath) {
        File file = new File(filePath);

        // 获取文件名字 默认为最后一个/后的部分
        String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

        // 获取文件后缀，用来设置ContentType
        String fileSuffix = null;
        try {
            fileSuffix = filePath.substring(filePath.lastIndexOf(".") + 1);
        } catch (Exception e) {
            logger.error("文件没有后缀名！使用二进制进行下载");
        }
        String contentType = MIME_MAP.get(fileSuffix) == null ? "application/octet-stream" : MIME_MAP.get(fileSuffix);

        HttpServletResponse response = ServletUtils.getResponse();
        response.setContentType(contentType);
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));

        try (InputStream in = new FileInputStream(file); ServletOutputStream out = response.getOutputStream()) {
            IOUtils.copy(in, out);
        } catch (IOException e) {
            logger.error("下载文件失败！文件路径：[{}]", filePath);
        }
    }

    /**
     * 导出 Map 复合数据到Excel
     * 场景：一个 key 对应多个 value，即一个单元格对应多个单元格。类似于树形结构
     *
     * @param map      Map数据
     * @param fileName 文件名（不含后缀）
     * @param <K>      Map的Key类型
     * @param <V>      Map的Value中List的类型
     */
    public static <K, V> void exportMapToExcel(Map<K, List<V>> map, String fileName) {
        try {
            // 创建工作簿
            Workbook workbook = new XSSFWorkbook();

            // 处理Map数据
            createMapSheet(workbook, "数据列表", map);

            // 输出文件
            HttpServletResponse response = ServletUtils.getResponse();
            response.reset();
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
            response.setHeader("Content-Disposition",
                    "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + ".xlsx");

            try (ServletOutputStream out = response.getOutputStream()) {
                workbook.write(out);
                out.flush();
            }
            workbook.close();
        } catch (Exception e) {
            logger.error("导出Map数据Excel失败！", e);
        }
    }

    /**
     * 创建Map数据的Sheet
     */
    private static <K, V> void createMapSheet(Workbook workbook, String sheetName, Map<K, List<V>> data) {
        if (data == null || data.isEmpty()) {
            return;
        }

        Sheet sheet = workbook.createSheet(sheetName);

        // 创建标题样式
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerStyle.setFont(headerFont);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);

        // 创建key样式
        CellStyle keyStyle = workbook.createCellStyle();
        Font keyFont = workbook.createFont();
        keyFont.setBold(true);
        keyStyle.setFont(keyFont);
        keyStyle.setAlignment(HorizontalAlignment.LEFT);
        keyStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 创建标题行
        Row headerRow = sheet.createRow(0);
        Cell keyHeader = headerRow.createCell(0);
        Cell valueHeader = headerRow.createCell(1);
        keyHeader.setCellValue("Key");
        valueHeader.setCellValue("Values");
        keyHeader.setCellStyle(headerStyle);
        valueHeader.setCellStyle(headerStyle);

        // 写入数据 聚合
        int rowIndex = 1;
        for (Map.Entry<K, List<V>> entry : data.entrySet()) {
            K key = entry.getKey();
            List<V> values = entry.getValue();

            if (values == null || values.isEmpty()) {
                // 如果没有值，创建一个空行
                Row row = sheet.createRow(rowIndex++);
                Cell keyCell = row.createCell(0);
                keyCell.setCellValue(key != null ? key.toString() : "");
                keyCell.setCellStyle(keyStyle);
            } else {
                // 创建合并的key单元格
                int startRow = rowIndex;
                int endRow = rowIndex + values.size() - 1;

                // 为每个value创建一行
                for (V value : values) {
                    Row row = sheet.createRow(rowIndex++);

                    // 第一行设置key
                    if (row.getRowNum() == startRow) {
                        Cell keyCell = row.createCell(0);
                        keyCell.setCellValue(key != null ? key.toString() : "");
                        keyCell.setCellStyle(keyStyle);
                    }

                    // 设置value
                    Cell valueCell = row.createCell(1);
                    valueCell.setCellValue(value != null ? value.toString() : "");
                }

                // 合并key单元格
                if (values.size() > 1) {
                    sheet.addMergedRegion(new CellRangeAddress(
                            startRow,    // 起始行
                            endRow,      // 结束行
                            0,           // 起始列
                            0            // 结束列
                    ));
                }
            }
        }

        // 调整列宽
        sheet.setColumnWidth(0, 25 * 256);  // key列
        sheet.setColumnWidth(1, 40 * 256);  // value列

        // 冻结首行
        sheet.createFreezePane(0, 1);
    }
}
